#include "header.h"
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/namei.h>
#include <linux/sched.h>
#include <linux/dcache.h>
#include <linux/kthread.h>
#include <linux/netlink.h>
#include <linux/syscalls.h>
#include <net/sock.h>
#define NUM_LEN 64
#define PATH_LEN 512

struct sock* __server_sock = NULL;
args_queue_elem* __args_queue_head = NULL;
args_queue_elem* __args_queue_tail = NULL;

void __compose_abs_path(char* abs_path, size_t from, file_node* node)
{
	char tmp[PATH_LEN];
	char* idx_from = NULL;
	char* idx_to = NULL;
	memset(tmp, 0, PATH_LEN);
	memset(abs_path+from, 0, PATH_LEN-from);
	while (PA(node)) {
		if (tmp[strlen(tmp)] != '\0') {
			tmp[strlen(tmp)] = '/';
		}
		strcpy(tmp+strlen(tmp), node->dentry->d_name.name);
		node = PA(node);
	}
	idx_from = tmp + strlen(tmp) - 1;
	idx_to = abs_path + strlen(abs_path);
	do {
		*idx_to = *idx_from;
		if (idx_from == tmp) break;
		else {
			idx_to++;
			idx_from--;
		}
	} while (1); 
}

void cat_abs_path(char* buf, file_node* node)
{
	char c;
	int len = 0, idx = 0;
	if (!buf || !node) return;
	while (node) {
		strcat(buf, node->dentry->d_name.name);
		if (PA(node)) strcat(buf, "/");  
		node = PA(node);
	}
	
	len = strlen(buf);
	if (!len) return;
	for (; idx < len/2; ++idx) {
		c = buf[idx];
		buf[idx] = buf[len-1-idx];
		buf[len-1-idx] = c;
	}
}

void __create_path(char* abs_path, file_node* node, args_queue_elem* elem)
{
	int error = 0;
	struct dentry* dentry = NULL;
	struct file* file = NULL;
	size_t len = strlen(abs_path);
	sbt sbt_from = elem->sbt_from;
	sbt sbt_to = elem->sbt_to;
	if (!abs_path || !node || !elem) return;
	strcat(abs_path, "/");
	strcat(abs_path, node->dentry->d_name.name);
	if (S_ISREG(node->dentry->d_inode->i_mode)) {
		file = filp_open(abs_path, O_CREAT|O_RDWR, 0666);
		record_blocks(node->dentry->d_inode, file->f_dentry->d_inode, sbt_from, sbt_to);
		file->f_dentry->d_inode->i_private = elem;
		replace_make_request(file->f_dentry->d_sb, elem);
		fput(file);
	} else if (S_ISLNK(node->dentry->d_inode->i_mode)) {
	} else if (S_ISCHR(node->dentry->d_inode->i_mode)) {
	} else if (S_ISBLK(node->dentry->d_inode->i_mode)) {
	} else if (S_ISFIFO(node->dentry->d_inode->i_mode)) {
	} else if (S_ISSOCK(node->dentry->d_inode->i_mode)) {
	} else {
		struct nameidata nd;
		if (STR_EQUAL(node->dentry->d_name.name, "/")) goto next;
		if (!path_lookup(abs_path, LOOKUP_PARENT, &nd)) {
			dentry = lookup_create(&nd, 1);
			if (!IS_ERR(dentry)) { 
				error = vfs_mkdir(nd.path.dentry->d_inode, dentry, 0600);
				dput(dentry);
				mutex_unlock(&nd.path.dentry->d_inode->i_mutex);
			}
			path_put(&nd.path);
		}
	}
next:
	if (node->firstChd) __create_path(abs_path, node->firstChd, elem);
	abs_path[len] = '\0';
	if (node->nextSib) __create_path(abs_path, node->nextSib, elem);
}

args_queue_elem* __alloc_args_elem(void)
{
	args_queue_elem* elem = 
		(args_queue_elem*)kmalloc(sizeof(args_queue_elem), GFP_KERNEL);
	memset(elem, 0, sizeof(args_queue_elem));
	spin_lock_init(&elem->lock);
	elem->sbt_from = alloc_sbt();
	elem->sbt_to = alloc_sbt();
	atomic_set(&elem->should_freeze, 1);
	init_waitqueue_head(&elem->frez_cond);
	return elem;
}

size_t __append_args_elem(args_queue_elem* elem)
{
	if (!elem) return 0;
	
	if (__args_queue_tail) {
		elem->seq = __args_queue_tail->seq + 1;
		__args_queue_tail->next = elem;
		__args_queue_tail = elem;
	} else {
		elem->seq = 0;
		__args_queue_head = __args_queue_tail = elem;
	}

	return elem->seq;	
}

void __restore_mountto_make_request(const char* path) 
{
	struct nameidata nd;
	if (!path) return;
	if (path_lookup(path, LOOKUP_FOLLOW, &nd)) return;
	restore_make_request(nd.path.dentry->d_sb);
	path_put(&nd.path);
}

void __remove_args_elem(size_t seq)
{
	args_queue_elem* prev = NULL;
	args_queue_elem* elem = __args_queue_head;
	while (elem) {
		if (elem->seq == seq) {
			if (prev) {
				prev->next = elem->next;
				if (__args_queue_tail == elem) __args_queue_tail = prev;
			} else {
				if (__args_queue_tail == elem) 
					__args_queue_head = __args_queue_tail = NULL;
				else __args_queue_head = elem->next;
			}
			break;
		}
		prev = elem;
		elem = elem->next;
	}

	if (!elem) return;
	
	if (elem->sbt_from) {
		release_sbt(elem->sbt_from);
		kfree(elem->sbt_from);
		elem->sbt_from = NULL;
	}

	if (elem->sbt_to) {
		release_sbt(elem->sbt_to);
		kfree(elem->sbt_to);
		elem->sbt_to = NULL;
	}
	
	if (elem->mount_to) __restore_mountto_make_request(elem->mount_to);

	if (elem->ag) {
		clean_args(elem->ag);
		kfree(elem->ag);
		elem->ag = NULL;
	}

	if (elem->root) { 
		release_camera_tree(elem->root);
		elem->root = NULL;
	}
	
	kfree(elem);
}

args_queue_elem* __find_args_elem(size_t seq)
{
	args_queue_elem* elem = __args_queue_head;
	while (elem) {
		if (elem->seq == seq) return elem;
		elem = elem->next;
	}
	return NULL;
}

void clean_args_queue(void)
{
	while (__args_queue_head) {
		args_queue_elem* next = __args_queue_head->next;

		if (__args_queue_head->sbt_from) {
			release_sbt(__args_queue_head->sbt_from);
			kfree(__args_queue_head->sbt_from);
			__args_queue_head->sbt_from = NULL;
		}

		if (__args_queue_head->sbt_to) {
			release_sbt(__args_queue_head->sbt_to);
			kfree(__args_queue_head->sbt_to);
			__args_queue_head->sbt_to = NULL;
		}

		if (__args_queue_head->ag) {
			clean_args(__args_queue_head->ag);
			kfree(__args_queue_head->ag);
			__args_queue_head->ag = NULL;
		}
		
		if (__args_queue_head->root) {
			release_camera_tree(__args_queue_head->root);
			__args_queue_head->root = NULL;
		}
		
		kfree(__args_queue_head);

		__args_queue_head = next;
	}
}

args* __read_args(struct sk_buff* skb) 
{
	args* ag = NULL;
	int len = skb->len;
	char* str = NULL;
	char* last_args = NULL;
	struct nlmsghdr* nlh = nlmsg_hdr(skb);
	if (!skb) goto out;
	ag = (args*)kmalloc(sizeof(args), GFP_KERNEL);
	if (!ag) goto out;
	memset(ag, 0, sizeof(args));

	while (NLMSG_OK(nlh, len)) {
		str = (char*)NLMSG_DATA(nlh);
		if (STR_EQUAL(str, CAMERA_LONG)) last_args = CAMERA_LONG;
		else if (STR_EQUAL(str, EXCLUDE_LONG)) last_args = EXCLUDE_LONG;
		else if (STR_EQUAL(str, FILTER_LONG)) last_args = FILTER_LONG;
		else if (STR_EQUAL(str, TO_LONG)) last_args = TO_LONG;
		else if (STR_EQUAL(str, NAME_LONG)) last_args = NAME_LONG;
		else if (STR_EQUAL(str, MOUNT_LONG)) last_args = MOUNT_LONG;
		else if (STR_EQUAL(str, LOG_LONG)) last_args = LOG_LONG;
		else {
			if (STR_EQUAL(last_args, CAMERA_LONG)) {
				struct nameidata nd;	
				if (camera_path_tail) camera_path_tail = append_node(camera_path_tail, str);
				else camera_path_head = camera_path_tail = append_node(camera_path_tail, str);

				if (path_lookup(str, LOOKUP_FOLLOW, &nd)) goto out_failed;
				if (!block_size) block_size = 1<<nd.path.dentry->d_inode->i_blkbits;
				else if (block_size != (1<<nd.path.dentry->d_inode->i_blkbits)) goto out_failed;
				path_put(&nd.path);

			} else if (STR_EQUAL(last_args, EXCLUDE_LONG)) {
				if (exclude_path_tail) 
					exclude_path_tail = append_node(exclude_path_tail, str);
				else exclude_path_head = exclude_path_tail = append_node(NULL, str);
			} else if (STR_EQUAL(last_args, FILTER_LONG)) {
				if (filter_tail) filter_tail = append_node(filter_tail, str);
				else filter_head = filter_tail = append_node(filter_tail, str);
			} else if (STR_EQUAL(last_args, TO_LONG)) {
				set_save_to(ag, str);
			} else if (STR_EQUAL(last_args, NAME_LONG)) {
				set_name(ag, str);
			} else if (STR_EQUAL(last_args, MOUNT_LONG)) {
				set_mount_to(ag, str);
			} else if (STR_EQUAL(last_args, LOG_LONG)) {
				set_log_to(ag, str);
			}
		}
		nlh = NLMSG_NEXT(nlh, len);
	}
out:
	return ag;

out_failed:
	clean_args(ag);
	kfree(ag);	
	return NULL;
}

int check_is_ext4(struct file_node* root)
{
	int ret = 0;
	if (!root) goto out;
	while (root->firstChd) root = root->firstChd;
	if (IS_EXT4(FSNAME(root->dentry->d_inode))) ret = 1;
out:
	return ret;
}

void build_file_tree(struct sk_buff* skb) 
{
	u32 pid;
	args* ag = NULL;
	size_t seq;
	char num[NUM_LEN];
	struct net* net;
	int is_ext4 = 1;
	int tree_size = 0;
	struct sk_buff* back_skb;
	struct nlmsghdr* nlhdr;
	args_queue_elem* elem = NULL;
	file_node* root = NULL;

	back_skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_ATOMIC);
	if (!back_skb) {
		CAMERA_LOG("camera.ko failed to alloc backward skb.\n");
		return;
	}

	net = sock_net(skb->sk);
	pid = NETLINK_CB(skb).pid;
	NETLINK_CB(back_skb).pid = 0;
	nlhdr = nlmsg_put(back_skb, 0, 0, 0, NLMSG_SPACE(MAX_PAYLOAD), 0);
	memset(num, 0, NUM_LEN);

	elem = __alloc_args_elem();
	if (!elem) {
		sprintf(num, "%d;0;0;0", ALLOC_ELEM_ERR);
		goto send_reply;
	} else seq = __append_args_elem(elem);

	ag = __read_args(skb);
	if (!ag) {
		__remove_args_elem(seq);
		sprintf(num, "%d;0;0;0", READ_ARGS_ERR);
		goto send_reply;
	} else elem->ag = ag;

	root = build_cow_tree(ag, elem);
	if (!root) {
		__remove_args_elem(seq);
		sprintf(num, "%d;0:0;0", BUILD_TREE_ERR);
		goto send_reply;
	} else elem->root = root;

	tree_size = elem->all = calc_tree_size(root);
	is_ext4 = check_is_ext4(root);
	sprintf(num, "%d;%d;%lu;%d", tree_size, is_ext4, seq, block_size);

send_reply:
	strcpy((char*)NLMSG_DATA(nlhdr), num);
	netlink_unicast(__server_sock, back_skb, nlmsg_hdr(skb)->nlmsg_pid, MSG_DONTWAIT);
	return;
}

void __unfreeze_cond(args_queue_elem* elem)
{
	if (!elem) return;
	atomic_set(&elem->should_freeze, 0);
	wake_up_interruptible(&elem->frez_cond);
}

typedef struct {
	size_t seq;
} thread_param;

thread_param* __alloc_thread_param(size_t seq)
{
	thread_param* param = 
		(thread_param*)kmalloc(sizeof(thread_param), GFP_KERNEL);
	param->seq = seq;
	return param;
}

int __do_cow(void* param)
{
	int ret = 0;
	size_t seq = ((thread_param*)param)->seq;
	args* ag = NULL;
	char abs_path[PATH_LEN];

	args_queue_elem* elem = __find_args_elem(seq);
	if (!elem) {ret = FIND_ELEM_ERR; goto out;}

	ag = elem->ag;
	if (!ag) {ret = FIND_ARGS_ERR; goto out;}

	memset(abs_path, 0, PATH_LEN);
	strcpy(abs_path, mount_to);
	elem->sbt_from = alloc_sbt();
	elem->sbt_to = alloc_sbt();
	__create_path(abs_path, elem->root, elem);//elem->root, elem->sbt_from, elem->sbt_to);
	__unfreeze_cond(elem);
	copy_blocks(elem);
out:
	__remove_args_elem(seq);
	return ret;
}

int __start_cow_thread(size_t seq)
{
	int err = 0;
	struct task_struct* thrd;
	thread_param* param = __alloc_thread_param(seq);
	thrd = kthread_create(__do_cow, param, "camera cow");
	if (IS_ERR(thrd)) {
		CAMERA_LOG("Error: Camera failed to start cow thread!\n");
		err = PTR_ERR(thrd);
	} else wake_up_process(thrd);
	return err;
}

ssize_t query_progress(struct sk_buff* skb) 
{
	u32 pid;
	char* str;
	struct net* net;
	char num[NUM_LEN];
	size_t prog = 101;
	int len = skb->len;
	struct sk_buff* back_skb;
	struct nlmsghdr* nlhdr;
	args_queue_elem* elem;

	struct nlmsghdr* nlh = nlmsg_hdr(skb);
	if (NLMSG_OK(nlh, len)) nlh = NLMSG_NEXT(nlh, len);
	if (NLMSG_OK(nlh, len)) {
		str = (char*)NLMSG_DATA(nlh);
		elem = __find_args_elem((size_t)simple_strtol(str, NULL, 10));
		if (elem) prog = (size_t)(elem->done/elem->all*100);
		else prog = 100;
	}
	

	back_skb = alloc_skb(NLMSG_SPACE(MAX_PAYLOAD), GFP_ATOMIC);
	if (!back_skb) {
		CAMERA_LOG("Camera failed to alloc back skb.\n");
		return -1;
	}

	net = sock_net(skb->sk);
	pid = NETLINK_CB(skb).pid;
	NETLINK_CB(back_skb).pid = 0;
	nlhdr = nlmsg_put(back_skb, 0, 0, 0, NLMSG_SPACE(MAX_PAYLOAD), 0);
	memset(num, 0, NUM_LEN);
	sprintf(num, "%lu", prog);
	strcpy((char*)NLMSG_DATA(nlhdr), num);
	netlink_unicast(__server_sock, back_skb, nlmsg_hdr(skb)->nlmsg_pid, MSG_DONTWAIT);
	return 0;
}

void dispatch(struct sk_buff* skb)
{
	char* str = NULL;
	int len = skb->len;
	struct nlmsghdr* nlh = nlmsg_hdr(skb);
	if (NLMSG_OK(nlh, len)) {
		str = (char*)NLMSG_DATA(nlh);
		if (STR_EQUAL(str, CAMERA_LONG)) build_file_tree(skb);
		else if (STR_EQUAL(str, QUERY_PROGRESS)) query_progress(skb);
		else __start_cow_thread((size_t)simple_strtol(str, NULL, 10));
	}
}

int start_listen(void)
{
	__server_sock = netlink_kernel_create(&init_net, 
					      NETLINK_TEST, 0, dispatch,
					      NULL, THIS_MODULE);
	if (!__server_sock) return 1;		
	return 0;
}

void stop_listen(void)
{
	netlink_kernel_release(__server_sock);
}

